package mazeMultiThread;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Element that renders the maze
 * @author Dani
 *
 */
public class MazeContainer extends JPanel
{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	//dimensions of the maze in nodes+walls
	private int xsquares = 1;
	private int ysquares = 1;
	//JFrame where the object is located
	private JFrame g;
	//maze that will generate random mazes
	private Maze maze;
	//map of the maze and map of the maze in the previous render state
	private boolean[][] map;
	private boolean[][] buffMap;
	//number of times the object is being repainted per second
	private float frames = 0;
	//various colors for rendering the mazes
	private Color colorDef = new Color(60,180,60);
	private Color colorNew = Color.RED;
	private Color colorStack = Color.BLACK;
	private Color colorLeader = Color.YELLOW;
	private Color colorStatus = Color.ORANGE;
	private Color colorBackground = Color.BLUE;
	
	/**
	 * Builds the object and lauches maze generation in a separate thread
	 * @param xsquares width IN NODES of the maze
	 * @param ysquares height IN NODES of the maze
	 * @param g Jframe where the object is located
	 */
	public MazeContainer(int xsquares, int ysquares, JFrame g) {
		maze = new Maze(xsquares,ysquares);
		this.buffMap = new boolean[xsquares*2-1][ysquares*2-1];
		this.xsquares = xsquares*2-1;
		this.ysquares = ysquares*2-1;

		this.g = g;

		this.maze.start();
	}

	/**
	 * Paints the maze, background and debug status menu
	 */
	public void paintComponent(Graphics g) {
		this.updateMazeStatus();
		this.map = maze.getMap();
		
		double rowH = getHeight() / (double)this.ysquares;
		double colW = getWidth() / (double)this.xsquares;
		Graphics2D g2 = (Graphics2D) g;
		//g2.setStroke(new BasicStroke(1));
		
		this.drawBackground(g2, this.colorBackground);
		this.drawMaze(g2, colW, rowH);
		this.drawStatus(g2);

		GH.isPainted = true;
	}
	/**
	 * Fills the background of g with color
	 * @param g Graphics which background is gonna be painted
	 * @param color filler color
	 */
	private void drawBackground(Graphics2D g, Color color) {
		g.setColor(color);
		g.fillRect(0, 0, this.g.getWidth(), this.g.getHeight());		
	}
	/**
	 * Calls the rendering status function if PRINT_STATUS is enabled
	 * @param g graphics where status is drawn
	 */
	private void drawStatus(Graphics2D g) {
		if (GH.PRINT_STATUS) 
			this.printStatus(g, this.colorStatus);
	}
	/**
	 * Paints the current maze generation status
	 * @param g
	 * @param color Color to use when painting
	 */
	private void printStatus(Graphics2D g, Color color) {
		g.setColor(color);
		g.getFont().deriveFont(Font.BOLD);
		//separation between lines, starting Y position and stating X position
		int inc = 16;
		int startY = 10;
		int startX = 1;
		g.drawString(maze.getStackStatus(), startX, startY);
		g.drawString(maze.getVisitedPlacesStatus(), startX, startY+inc);
		g.drawString(maze.getPlacesLeftStatus(), startX, startY+2*inc);
		g.drawString(maze.getColisionesStatus(), startX, startY+3*inc);
		g.drawString(maze.getElapsedTime(), startX, startY+inc*4);
		g.drawString(maze.getCurrentPointData(),startX,startY+inc*5);
		
		String a = "";
		if (GH.PAUSE)
			a = " (Paused)";
		g.drawString("Speed: " + GH.MAZE_SLOWNESS + a, startX, startY+inc*6);
		
		g.drawString("Zoom : " + (float)((int)(GH.scX*100))/100 + "x", startX, startY+inc*7);
		
		g.drawString("" + (this.frames), (int) (getWidth()-g.getFontMetrics().getStringBounds(("" + (this.frames)), g).getWidth()), startY);
		
	}	
	/**
	 * Moves and scales the graphics to align the maze acording to user input, then draw it using rectangles
	 * @param g2 graphics that need to be scaled
	 * @param colW	rectangle width
	 * @param rowH  rectangle height
	 */
	private void drawMaze(Graphics2D g2, double colW, double rowH) {
		g2.translate((double)getWidth()*(1.0-GH.scX)/2.0+GH.xStart, (double)getHeight()*(1.0-GH.scY)/2.0+GH.yStart);
		g2.scale(GH.scX, GH.scY);
		
		this.drawMap(this.colorDef, this.colorNew, g2, colW, rowH, GH.PRINT_NEW);
		if (GH.PRINT_STACK)
			this.drawStack(this.colorStack, g2, colW, rowH);
		if (GH.PRINT_LEADER)
			this.drawLeader(colorLeader, g2, colW, rowH);
		
		g2.scale(1/GH.scX, 1/GH.scY);
		g2.translate(-(double)getWidth()*(1.0-GH.scX)/2.0-GH.xStart, -(double)getHeight()*(1.0-GH.scY)/2.0-GH.yStart);
		
	}
	/**
	 * Draws the maze map using rectangles for each cell
	 * @param colrPrev color of old cells (previous render)
	 * @param colrNew color of new cells 
	 * @param g2 graphics where it will be drawn
	 * @param colW	rectangle width
	 * @param rowH	rectangle height
	 * @param drawNew	if true, new cells will be drawn with a different color than previous cells
	 */
	private void drawMap(Color colrPrev, Color colrNew, Graphics2D g2, double colW, double rowH, boolean drawNew) {
		g2.setColor(colrPrev);
		Rectangle2D rect;
		for (int i = 0; i < this.xsquares; i++) {
			for (int j = 0; j < this.ysquares; j++) {
				if (this.map[i][j]) {
					if (drawNew) {
						if (this.buffMap[i][j]) {
							g2.setColor(colrPrev);
						}
						else {
							g2.setColor(colrNew);
						}
					}
					rect = new Rectangle2D.Double(colW*i, rowH*j, colW, rowH);
					g2.fill(rect);
					if (!GH.PAUSE) {
						this.buffMap[i][j] = true;
					}
				}
			}
		}
	}
	/**
	 * Draws the cells on the stack of the algorithm (places from where the maze could potentially grow)
	 * @param colorStack2	color of the stack cells
	 * @param g2	graphics where those will be drawn
	 * @param colW	rectangle width
	 * @param rowH	rectangle height
	 */
	private void drawStack(Color colorStack2, Graphics2D g2, double colW, double rowH) {
		ArrayList<Point> list = maze.getStack();
		Rectangle2D rect;
		g2.setColor(colorStack2);
		for (int i = 0; i < list.size(); i++) {
			try {
				Point p = list.get(i);
				if (p != null) {
					//g2.fillRect((int)(colW*p.getX()), (int)(rowH*p.getY()), (int)colW, (int)rowH);
					rect = new Rectangle2D.Double(p.getX()*colW, p.getY()*rowH, colW, rowH);
					g2.fill(rect);
					//g2.fill3DRect((int)(colW*p.getX()), (int)(rowH*p.getY()), (int)colW, (int)rowH, true);
				}
			} catch (Exception e) {
				System.err.println("Unsynchronized Thread caused null reference");
			}
		}
	}
	/**
	 * Draws the leader (top) cell on the stack of the generation algorithm
	 * Works like drawStack but only drawing the top cell
	 * @param colorLeader2	color of the leader
	 * @param g2	graphics where the leader is drawn
	 * @param colW	rectangle width
	 * @param rowH	rectangle height
	 */
	private void drawLeader(Color colorLeader2, Graphics2D g2, double colW, double rowH) {
		ArrayList<Point> list = maze.getStack();
		Rectangle2D rect;
		g2.setColor(colorLeader2);
		if (list.size()>0) {
			try {
				Point p = list.get(list.size()-1);
				if (p != null) {
					rect = new Rectangle2D.Double(p.getX()*colW, p.getY()*rowH, colW, rowH);
					g2.fill(rect);
					//g2.fill3DRect((int)(colW*p.getX()), (int)(rowH*p.getY()), (int)colW, (int)rowH, true);
				}
			} catch (Exception e) {
				System.err.println("Unsynchronized Thread caused null reference");
			}
		}
	}
	/**
	 * Updates some variables needed when the maze is restarted in order to draw the new maze properly
	 */
	private void updateMazeStatus() {
		if(GH.RESTART) {
			this.maze.end();
			this.xsquares = GH.MAZE_WIDTH*2-1;
			this.ysquares = GH.MAZE_HEIGHT*2-1;
			this.maze = new Maze(GH.MAZE_WIDTH,GH.MAZE_HEIGHT);
			this.maze.start();
			GH.RESTART = false;
			this.buffMap = new boolean[xsquares][ysquares];
		}
	}
	
	@SuppressWarnings("unused")
	private void printSimpleString(String s, int width, int XPos, int YPos, Graphics2D g){  
		int stringLen = (int)  g.getFontMetrics().getStringBounds(s, g).getWidth();  
		int stringHei = (int)  g.getFontMetrics().getStringBounds(s, g).getHeight();
		int start =  - stringLen/2;  
		int starty = stringHei/4;
		//int starty = 0;
		g.drawString(s, start + XPos, starty + YPos);  
	}


	/**
	 * sets the current number of FPS given by the caller of the repaint method
	 * @param f current FPS
	 */
	public void setFrames(float f) {
		frames = f;		
	}

}



